import { SinglyLinkedList } from '../rt-singly-linked-list'
import { error } from '@/library/tool-kits/log'

export type FnReturnPromise = (...arg: Array<any>) => Promise<any>

export type AliasFuntion = (...args: Array<any>) => any

/**
 * @description 限制并发数
 *
 * @example
 * ```
 *  const genderatePromise = (timeout: number, resolved = true) => new Promise((resolve, reject) => {
      setTimeout(() => {
        resolved ? resolve(timeout) : reject(timeout)
      }, timeout)
    })

    const limit = new Plimit(1)

    console.log(new Date().getSeconds())

    const input = [
      limit.of(() => genderatePromise(2000)),
      limit.of(() => genderatePromise(4000, false)),
      limit.of(() => genderatePromise(6000)),
    ]

    Promise.all(input)
      .then((res) => {
        console.log('==== res ====', res)
        console.log(new Date().getSeconds())
      })
      .catch((err) => {
        console.log('==== err ====', err)
        console.log(new Date().getSeconds())
      })
 * ```
 */
export class Plimit {
  /** 活跃数 */
  #activeCount = 0

  /** 并发数 default 1 */
  #concurrency = 1

  /** Queue */
  #queue = new SinglyLinkedList<FnReturnPromise>()

  constructor(concurrency = 1) {
    this.#concurrency = concurrency
  }

  get activeCount(): number {
    return this.#activeCount
  }

  private set activeCount(count: number) {
    this.#activeCount = count
  }

  private get concurrency(): number {
    return this.#concurrency
  }

  get queue(): SinglyLinkedList<FnReturnPromise> {
    return this.#queue
  }

  get pendingCount(): number {
    return this.queue.size
  }

  /**
   * @param {FnReturnPromise} fn - 返回promise实例函数
   * @param {Array<any>} ...args - fn实参
   * @returns {Promise<any>} Promise实例
   */
  of(fn: FnReturnPromise, ...args: Array<any>): Promise<any> {
    return new Promise((resolve) => {
      this.enqueue(fn, resolve, args)
    })
  }

  private enqueue(fn: FnReturnPromise, resolve: AliasFuntion, args: Array<any>) {
    this.queue.push(this.run.bind(this, fn, resolve, args));
    (async () => {
      await Promise.resolve()
      if (this.activeCount < this.concurrency && this.queue.size > 0) {
        (this.queue.shift() as AliasFuntion)()
      }
    })()
  }

  private async run(fn: FnReturnPromise, resolve: AliasFuntion, args: Array<any>) {
    this.activeCount++
    /**
     * 即使函数内有同步错误, 也会被转换为 Promise rejection
     * 所有错误都通过 Promise 传播 不会意外抛出未捕获的同步异常
     */
    const result = (async () => fn(...args))()
    resolve(result) // 将Promise暴露给调用方

    /**
     * 这里的await主要作用：
     * 1. 等待任务完成以更新队列状态
     * 2. 捕获未被外部处理的错误
     */
    try {
      await result
    } catch (err: any) {
      error('pLimit', err.toString())
    }

    this.next()
  }

  private next(): void {
    this.activeCount--

    if (this.queue.size > 0) {
      (this.queue.shift() as AliasFuntion)()
    }
  }

  clear(): void {
    this.queue.clear()
  }
}
